package cn.zzzhy.core.db.builder;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class GenericSQLBuilder implements ISQLBuilder {
	protected String table;

	protected List<String> columns = Lists.newArrayList();
	protected List<String> joins = Lists.newArrayList();
	protected List<String> conditions = Lists.newArrayList();
	protected List<String> groupBys = Lists.newArrayList();
	protected List<String> havings = Lists.newArrayList();
	protected List<String> orderBys = Lists.newArrayList();

	protected boolean lockForUpdate;

	protected Integer offset;
	protected Integer rowNumber;

	protected List<Object> parameters = Lists.newArrayList();

	@Override
	public ISQLBuilder clone() {
		try {
			GenericSQLBuilder clone = (GenericSQLBuilder) super.clone();
			clone.columns = new ArrayList<String>(columns);
			clone.joins = new ArrayList<String>(joins);
			clone.conditions = new ArrayList<String>(conditions);
			clone.groupBys = new ArrayList<String>(groupBys);
			clone.havings = new ArrayList<String>(havings);
			clone.orderBys = new ArrayList<String>(orderBys);
			clone.parameters = new ArrayList<Object>(parameters);
			clone.lockForUpdate = lockForUpdate;
			return clone;
		} catch (CloneNotSupportedException e) {
			throw new RuntimeException(e);
		}
	}

	public void setTable(String table) {
		this.table = table;
	}

	public Object[] getParameters() {
		return parameters.toArray();
	}

	public String toCountSQL() {
		if(groupBys.isEmpty()){
			StringBuilder sb = new StringBuilder("select count(*) from");
			sb.append(table).append(toJoinSQL());
			assembleCondition(sb);
			return sb.toString();
		}else {
			return "select count(*) from ("
					+ toSQL0(true, false, false)
					+ ") a";
		}
	}

	public String toSQL() {
		StringBuilder sb = new StringBuilder("select ");
		if(columns.isEmpty()){
			sb.append(" * ");
		}else{
			sb.append(toSelectColumns());
		}
		sb.append(" from ").append(table);
		sb.append(toSQL0(true,true,true));
		return sb.toString();
	}

	private String toSQL0(boolean withGroup, boolean withOrderBy, boolean withLimit) {
		StringBuilder sb = new StringBuilder();
		sb.append(toJoinSQL());
		if(withGroup){
			sb.append(toGroupBySQL()).append(toHavingSQL());
		}
		if(withOrderBy){
			sb.append(toOrderBySQL());
		}
		if(withLimit){
			sb.append(toLimitSQL());
		}
		return sb.toString();
	}

	protected String toSelectColumns() {
		if (columns.isEmpty()) {
			return "*";
		}
		return Joiner.on(", ").join(columns);
	}

	protected String toJoinSQL() {
		if (joins.isEmpty()) {
			return "";
		}
		return "\n" + Joiner.on("\n").join(joins);
	}

	protected String toGroupBySQL() {
		if (groupBys.isEmpty()) {
			return "";
		}

		return " group by " + Joiner.on(", ").join(groupBys);
	}

	protected String toHavingSQL() {
		if (havings.isEmpty()) {
			return "";
		}
		return " having " + Joiner.on(" and ").join(havings);
	}

	protected String toOrderBySQL() {
		if (orderBys.isEmpty()) {
			return "";
		}

		return " order by " + Joiner.on(", ").join(orderBys);
	}

	protected String toLimitSQL() {
		if (offset == null) {
			return "";
		}
		return " limit " + offset + ", " + rowNumber;
	}


	private void assembleCondition(StringBuilder sb){
		if(conditions != null){
			for (int i = 0; i < conditions.size(); i++) {
				if(i > 0){
					sb.append(" and ");
				}
				sb.append(conditions.get(i));
			}
		}
	}

	public void where(String condition, Object... params) {
		conditions.add(condition);
		Collections.addAll(parameters, params);
	}

	public void select(String... columns) {
		this.columns.addAll(Lists.newArrayList(columns));
	}

	public void clearSelect() {
		this.columns.clear();
	}

	public void join(String joinSql) {
		joins.add(joinSql);
	}

	public void groupBy(String groupBy) {
		groupBys.add(groupBy);
	}

	public void having(String having) {
		havings.add(having);
	}

	public void orderBy(String orderBy) {
		orderBys.add(orderBy);
	}

	public void limit(Integer offset, Integer rowNumber) {
		this.offset = offset;
		this.rowNumber = rowNumber;
	}

	public boolean hasLimit() {
		return offset == null;
	}

	public void lock() {
		lockForUpdate = true;
	}

	public String getLastInsertIdSQL() {
		return null;
	}

}